package fir

import spinal.core.{SFix, _}
import spinal.lib._




case class DirectFormFirWithStream(taps : Int, width : Int) extends Component{
  val io = new Bundle {
    val factor = slave Stream (Vec(SFix(0 exp, 16 bits), taps))
    val input = slave Stream (SFix(width - 1 exp, width bits))
    val output = master Stream (SInt(width bits))
  }

  def multiAndSum(sourcesInput : Stream[SFix], sourcesFactor : Stream[Vec[SFix]]) : SInt = new Area{
    val hist          = History(that = sourcesInput.payload, length = taps, when =
      sourcesInput.valid & sourcesFactor.valid)
    val multiResult   = (hist, sourcesFactor.payload).zipped.map((x, y) => x * y)
    val summaryResult = multiResult.reduceBalancedTree(_ + _)
  }.summaryResult.toSInt.resize(width)

  io.output << StreamJoin.arg(io.input, io.factor).translateWith(multiAndSum(io.input, io.factor))
}

case class TransposeFormFirWithStream(taps : Int, width : Int) extends Component{
  def multiAndSum(sourcesInput : Stream[SFix], sourcesFactor : Stream[Vec[SFix]]) : SInt = new Area{
    val multiResult = sourcesFactor.payload.map(x => x * sourcesInput.payload)
    val sumReg      = Vec(Reg(SFix(width - 1 exp, width bits))  init(0), taps)
    when(sourcesInput.valid){
      for (i <- 0 until taps - 1) {
        sumReg(i) := (multiResult(i).truncated + sumReg(i + 1)).truncated
      }
      sumReg(taps - 1) := multiResult(taps - 1).truncated
    }
  }.sumReg(0).toSInt.resize(width bits)

  val io = new Bundle{
    val factor = slave Stream(Vec(SFix(0 exp, 16 bits), taps))
    val input  = slave Stream(SFix(width - 1 exp, width bits))
    val output = master Stream(SInt(width bits))
  }

  io.output << StreamJoin(io.input, io.factor).translateWith(multiAndSum(io.input, io.factor))
}

case class LinearPhaseFormFirWithStream(taps : Int, width : Int) extends Component{
  def multiAndSum(sourcesInput : Stream[SFix], sourcesFactor : Stream[Vec[SFix]]) : SInt = new Area{
    val hist = History(that = sourcesInput.payload.toSInt.resize(width + 1).toSFix,
                       length = taps,
                       when = sourcesInput.valid & sourcesFactor.valid,
                       init = toSFix(0))
    val result = SInt(width bits)
    if(taps % 2 == 0){
      val foldFactorResult  = (hist.dropRight(taps / 2), hist.drop(taps / 2).reverse, sourcesFactor.payload.dropRight(taps / 2)).zipped
      val multiResult       = foldFactorResult.map((x, y, z) => (x + y) * z)
      val summaryResult     = multiResult.reduceBalancedTree(_ + _)
      result := summaryResult.toSInt.resize(width)
    }else{
      val foldFactorResult  = (hist.dropRight(taps / 2 + 1), hist.drop(taps / 2 + 1).reverse, sourcesFactor.payload.dropRight(taps / 2 + 1)).zipped
      val multiResult       = foldFactorResult.map((x, y, z) => (x + y) * z)
      val summaryResult     = multiResult.reduceBalancedTree(_ + _) + sourcesFactor.payload(taps / 2) * hist(taps / 2)
      result := summaryResult.toSInt.resize(width)
    }
  }.result

  val io = new Bundle{
    val factor = slave Stream(Vec(SFix(0 exp, 16 bits), taps))
    val input  = slave Stream(SFix(width - 1 exp, width bits))
    val output = master Stream(SInt(width bits))
  }

  io.output << StreamJoin.arg(io.input, io.factor).translateWith(multiAndSum(io.input, io.factor))
}


//Define a custom SpinalHDL configuration with synchronous reset instead of the default asynchronous one. This configuration can be resued everywhere
object FirConfig extends SpinalConfig(defaultConfigForClockDomains = ClockDomainConfig(resetKind = SYNC))

//Generate the MyTopLevel's Verilog using the above custom configuration.
object FirVerilogWithCustomConfig {
  def main(args: Array[String]) {
    FirConfig.generateVerilog(new LinearPhaseFormFirWithStream(4,8))
  }
}